package org.budo.mongo.jdbc.driver.parser.jsqlparser;

import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.budo.jdbc.driver.exception.BudoJdbcDriverNotSupportedException;
import org.budo.jdbc.driver.exception.SubSelectNotSupportedException;
import org.budo.mongo.jdbc.driver.BudoMongoJdbcResultSetMetaData;
import org.budo.mongo.jdbc.driver.parser.StatementParserUtil;
import org.budo.mongo.jdbc.driver.statement.AbstractMongoStatement;

import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.JdbcParameter;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.Limit;
import net.sf.jsqlparser.statement.select.OrderByElement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.select.SubSelect;
import net.sf.jsqlparser.statement.update.Update;

/**
 * @author li
 */
public class JSqlStatementParserUtil {
    static BudoMongoJdbcResultSetMetaData findStatementResultSetMetaData(Statement statement) {
        List<SelectItem> selectItems = JSqlStatementParserUtil.getSelectItems(statement);

        String[] columnNames = new String[selectItems.size()];

        for (int i = 0; i < selectItems.size(); i++) {
            SelectItem selectItem = selectItems.get(i);
            columnNames[i] = selectItem.toString();
        }

        return StatementParserUtil.resultSetMetaData(columnNames, Types.CHAR, Object.class);
    }

    static BudoMongoJdbcResultSetMetaData countStatementResultSetMetaData(Statement statement) {
        List<SelectItem> selectItems = JSqlStatementParserUtil.getSelectItems(statement);

        String[] columnNames = new String[selectItems.size()];

        for (int i = 0; i < selectItems.size(); i++) {
            SelectItem selectItem = selectItems.get(i);
            columnNames[i] = selectItem.toString();
        }

        return StatementParserUtil.resultSetMetaData(columnNames, Types.BIGINT, Long.class);
    }

    static BudoMongoJdbcResultSetMetaData aggregateStatementResultSetMetaData(Statement statement) {
        List<SelectItem> selectItems = JSqlStatementParserUtil.getSelectItems(statement);

        String[] columnNames = new String[selectItems.size()];

        for (int i = 0; i < selectItems.size(); i++) {
            SelectItem selectItem = selectItems.get(i);
            columnNames[i] = selectItem.toString();
        }

        return StatementParserUtil.resultSetMetaData(columnNames, Types.BIGINT, Long.class);
    }

    static String fieldName(Expression expression) {
        if (expression instanceof Column) {
            Column column = (Column) expression;
            return column.getColumnName();
        } else {
            throw new BudoJdbcDriverNotSupportedException("expression=" + expression + ", expression.type=" + expression.getClass());
        }
    }

    static boolean isCountStatement(PlainSelect plainSelect) {
        List<SelectItem> selectItems = plainSelect.getSelectItems();
        if (null != selectItems && 1 == selectItems.size()) {
            SelectItem selectItem = selectItems.get(0);
            if (selectItem instanceof SelectExpressionItem) {
                SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
                Expression expression = selectExpressionItem.getExpression();
                if (expression instanceof Function) {
                    Function function = (Function) expression;
                    String functionName = function.getName();
                    if ("COUNT".equalsIgnoreCase(functionName)) {
                        if (function.isAllColumns()) {
                            return true;
                        }

                        ExpressionList expressionList = function.getParameters();
                        if (null != expressionList) {
                            List<Expression> expressions = expressionList.getExpressions();
                            if (1 == expressions.size()) {
                                Expression expressionInFunction = expressions.get(0);
                                if (expressionInFunction instanceof LongValue) {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
        }

        return false;
    }

    /**
     * @see org.budo.mongo.jdbc.driver.parser.jsqlparser.JSqlParserMongoStatementParser#mongoAggregateStatement(String, Statement, PlainSelect)
     */
    public static boolean isAggregateStatement(PlainSelect plainSelect) {
        List<SelectItem> selectItems = plainSelect.getSelectItems();
        if (null != selectItems && 1 == selectItems.size()) {
            SelectItem selectItem = selectItems.get(0);
            if (selectItem instanceof SelectExpressionItem) {
                SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
                Expression expression = selectExpressionItem.getExpression();
                if (expression instanceof Function) {
                    Function function = (Function) expression;
                    String functionName = function.getName();
                    if ("MAX".equalsIgnoreCase(functionName) || "MIN".equalsIgnoreCase(functionName) || "AVG".equalsIgnoreCase(functionName) || "SUM".equalsIgnoreCase(functionName)) {
                        ExpressionList expressionList = function.getParameters();

                        if (null != expressionList) {
                            List<Expression> expressions = expressionList.getExpressions();
                            if (1 == expressions.size()) {
                                Expression expressionInFunction = expressions.get(0);
                                if (expressionInFunction instanceof Column) {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
        }

        return false;
    }

    public static Function getSelectFunction(PlainSelect plainSelect) {
        List<SelectItem> selectItems = plainSelect.getSelectItems();
        SelectItem selectItem = selectItems.get(0);
        SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
        Expression expression = selectExpressionItem.getExpression();
        return (Function) expression;
    }

    public static Column getSelectFunctionParameter(PlainSelect plainSelect) {
        List<SelectItem> selectItems = plainSelect.getSelectItems();
        SelectItem selectItem = selectItems.get(0);
        SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
        Expression expression = selectExpressionItem.getExpression();
        Function function = (Function) expression;
        ExpressionList expressionList = function.getParameters();

        List<Expression> expressions = expressionList.getExpressions();
        Expression expressionInFunction = expressions.get(0);
        return (Column) expressionInFunction;
    }

    static List<SelectItem> getSelectItems(Statement statement) {
        if (statement instanceof Select) {
            Select select = (Select) statement;
            SelectBody selectBody = select.getSelectBody();
            if (selectBody instanceof PlainSelect) {
                PlainSelect plainSelect = (PlainSelect) selectBody;
                return plainSelect.getSelectItems();
            }
        }

        throw new BudoJdbcDriverNotSupportedException("statement=" + statement + ", type=" + statement.getClass());
    }

    static Table getUpdateTable(String sql, Update update) {
        List<Table> tables = update.getTables();
        if (tables.size() > 1) {
            throw new BudoJdbcDriverNotSupportedException("sql=" + sql + ", 不支持同时更新多个表");
        }

        Table table = tables.get(0);
        return table;
    }

    static Table getFromTable(Statement statement) {
        if (statement instanceof Select) {
            Select select = (Select) statement;
            SelectBody selectBody = select.getSelectBody();
            if (selectBody instanceof PlainSelect) {
                PlainSelect plainSelect = (PlainSelect) selectBody;
                FromItem fromItem = plainSelect.getFromItem();
                if (fromItem instanceof Table) {
                    return (Table) fromItem;
                } else if (fromItem instanceof SubSelect) {
                    throw new SubSelectNotSupportedException("SubSelect not supported, statement=" + statement);
                }
            }
        }

        throw new BudoJdbcDriverNotSupportedException("statement=" + statement + ", type=" + statement);
    }

    static boolean isFindStatement(PlainSelect plainSelect) {
        List<SelectItem> selectItems = plainSelect.getSelectItems();
        for (SelectItem selectItem : selectItems) {
            if (selectItem instanceof SelectExpressionItem) {
                SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
                Expression expression = selectExpressionItem.getExpression();
                if (expression instanceof Column) {
                    //
                } else if (expression instanceof SubSelect) {
                    // 支持子查询
                    // throw new SubSelectNotSupportedException("SubSelect not supported, plainSelect=" + plainSelect);
                } else {
                    throw new BudoJdbcDriverNotSupportedException("expression=" + expression + ", expression.type=" + expression.getClass() + " plainSelect=" + plainSelect);
                }
            }
        }

        return true;
    }

    static List<Map<String, Object>> insertDocuments(Insert insert) {
        ItemsList itemsList = insert.getItemsList();
        List<Column> columns = insert.getColumns();
        if (itemsList instanceof ExpressionList) {
            ExpressionList expressionList = (ExpressionList) itemsList;
            List<Expression> expressions = expressionList.getExpressions();

            List<Map<String, Object>> insertDocuments = new ArrayList<Map<String, Object>>();
            Map<String, Object> insertDocument = valueDocument(columns, expressions);
            insertDocuments.add(insertDocument);
            return insertDocuments;
        } else if (itemsList instanceof MultiExpressionList) {
            MultiExpressionList multiExpressionList = (MultiExpressionList) itemsList;
            List<ExpressionList> expressionLists = multiExpressionList.getExprList();
            List<Map<String, Object>> insertDocuments = new ArrayList<Map<String, Object>>();
            for (ExpressionList expressionList : expressionLists) {
                List<Expression> expressions = expressionList.getExpressions();
                Map<String, Object> insertDocument = valueDocument(columns, expressions);
                insertDocuments.add(insertDocument);
            }
            return insertDocuments;
        } else {
            if (null != insert.getSelect()) {
                throw new SubSelectNotSupportedException("SubSelect not supported, insert=" + insert);
            } else {
                throw new BudoJdbcDriverNotSupportedException("itemsList=" + itemsList + ", type=" + (null == itemsList ? "null" : itemsList.getClass()) + ", insert=" + insert);
            }
        }
    }

    static Map<String, Object> valueDocument(Update update) {
        List<Column> columns = update.getColumns();
        List<Expression> expressions = update.getExpressions();
        Map<String, Object> valueDocument = valueDocument(columns, expressions);
        return valueDocument;
    }

    private static Map<String, Object> valueDocument(List<Column> columns, List<Expression> expressions) {
        Map<String, Object> document = new HashMap<String, Object>();
        for (int i = 0; i < expressions.size(); i++) {
            Column column = columns.get(i);
            Expression expression = expressions.get(i);

            String key = column.getColumnName();
            Object parameterValue = parameterValue(expression);

            document.put(key, parameterValue);
        }
        return document;
    }

    static Object parameterValue(Expression expression) {
        if (null == expression) {
            return null;
        }

        if (expression instanceof StringValue) {
            StringValue stringValue = (StringValue) expression;
            return stringValue.getValue();
        }

        if (expression instanceof LongValue) {
            LongValue longValue = (LongValue) expression;
            return longValue.getValue();
        }

        if (expression instanceof JdbcParameter) {
            JdbcParameter jdbcParameter = (JdbcParameter) expression;
            return StatementParserUtil.JDBC_PARAMETER_PREFIX + jdbcParameter.getIndex() + StatementParserUtil.JDBC_PARAMETER_SUFFIX;
        }

        throw new BudoJdbcDriverNotSupportedException("expression is " + expression + ", type=" + expression.getClass());
    }

    static Map<String, Object> limitDocument(Limit limit) {
        if (null == limit) {
            return null;
        }

        Map<String, Object> document = new HashMap<String, Object>();

        Object skip = parameterValue(limit.getOffset());
        document.put(AbstractMongoStatement.PAGE_SKIP, skip);

        Object rowCount = parameterValue(limit.getRowCount());
        document.put(AbstractMongoStatement.PAGE_LIMIT, rowCount);

        return document;
    }

    static Map<String, Object> orderByDocument(List<OrderByElement> orderByElements) {
        if (null == orderByElements || orderByElements.isEmpty()) {
            return null;
        }

        Map<String, Object> document = new HashMap<String, Object>();
        for (OrderByElement orderByElement : orderByElements) {
            int orderValue = orderByElement.isAsc() ? 1 : -1; // 正序倒序
            Expression expression = orderByElement.getExpression();
            if (expression instanceof Column) {
                Column column = (Column) expression;
                String columnName = column.getColumnName();
                document.put(columnName, orderValue);
            } else if (expression instanceof JdbcParameter) {
                JdbcParameter jdbcParameter = (JdbcParameter) expression;
                String orderByColumn = StatementParserUtil.JDBC_PARAMETER_PREFIX + jdbcParameter.getIndex() + StatementParserUtil.JDBC_PARAMETER_SUFFIX;
                document.put(orderByColumn, orderValue);
            } else {
                throw new BudoJdbcDriverNotSupportedException("orderByElement.expression=" + expression + ", type=" + expression.getClass());
            }
        }
        return document;
    }

    static boolean hasSubSelect(PlainSelect plainSelect) {
        FromItem fromItem = plainSelect.getFromItem();
        if (fromItem instanceof SubSelect) {
            return true;
        }

        return false;
    }
}
